import datamap
import random
from pygame.locals import *
from minisprite import *
from driver import *
from tools import *

##################################################
# terrain
##################################################
class Brick(StaticSprite, VitalSprite, CollisionSprite):
    """ can shot, and not through """
    HP = 1
    def __init__(self, node, datamap):
        image = load_image('res\\brick.bmp', (0, 0, 0))
        super(Brick, self).__init__(node, datamap)
        self.stc_init((image, ), 0)
        self.vtl_init(Brick.HP)
        self.col_init()
        self.col_register((Bullet, ), self.pros_bulletcoll)
        
    def pros_bulletcoll(self, spr):
        self.vtl_change(spr.power)
        if not self.vtl_alive(): self.kill()
            
    def update(self):
        self.col_collisionsig()
        super(Brick, self).update()

class Steel(StaticSprite, VitalSprite, CollisionSprite):
    """ can shot but it strong, not through """
    HP = 2
    def __init__(self, node, datamap):
        image = load_image('res\\steel.bmp', (0, 0, 0))
        super(Steel, self).__init__(node, datamap)
        self.stc_init((image, ), 0)
        self.vtl_init(Steel.HP)
        self.col_init()
        self.col_register((Bullet, ), self.pros_bulletcoll)
        
    def pros_bulletcoll(self, spr):
        self.vtl_change(spr.power, True)
        if not self.vtl_alive(): self.kill()
            
    def update(self):
        self.col_collisionsig()
        super(Steel, self).update()

class Lawn(StaticSprite, ThroughSprite):
    """ can through, and will cover tank """
    LAYER = 1
    def __init__(self, node, datamap):
        image = load_image('res\\lawn.bmp', (0, 0, 0))
        super(Lawn, self).__init__(node, datamap, layer = Lawn.LAYER)
        self.stc_init((image, ), 0)
        self.tru_init()
        self.tru_register((Player, Enemy, Bullet, BossEnemy))

class River(DynamicSprite, ThroughSprite):
    """ tank can't through but bullet can """
    LAYER = -1
    def __init__(self, node, datamap):
        frame = load_frame('res\\river.bmp', (2, 1), (0, 0, 0))
        super(River, self).__init__(node, datamap, layer = River.LAYER, logical = True)
        self.dnc_init((frame, ), 0, 20)
        self.tru_init()
        self.tru_register((Plane, Bullet, BossEnemy))
        
    def update(self):
        self.dnc_animate()

class Ice(StaticSprite, ThroughSprite):
    """ can through, and tank will cover this """
    LAYER = -1
    def __init__(self, node, datamap):
        image = load_image('res\\ice.bmp', (0, 0, 0))
        super(Ice, self).__init__(node, datamap, layer = Ice.LAYER)
        self.stc_init((image, ), 0)
        self.tru_init()
        self.tru_register((Player, Plane, Enemy, Bullet, BossEnemy))
        
##################################################
# special effect
##################################################
class Effect(DynamicSprite, ThroughSprite):
    """ special effect's super """
    LAYER = 2
    def __init__(self, node, datamap, framedict, frameindex, rate = 10, time = 0, deadfunc = None):
        super(Effect, self).__init__(node, datamap, layer = Effect.LAYER)
        self.__time = time
        if self.__time == 0:
            for fr in framedict:
                self.__time += len(fr) * rate # each image show tick
        self.__deadfunc = deadfunc
        self.dnc_init(framedict, frameindex, rate)
        self.tru_init()
        self.tru_register((Player,Plane,Enemy, Bullet))
        
    def update(self):
        self.dnc_animate()
        self.__time -= 1
        if self.__time <= 0: # if time down, kill self
            if self.__deadfunc: self.__deadfunc() # call this before dead
            self.kill()
        self.driver.clear()
        super(Effect, self).update()

class StarEffect(Effect, ProducerSprite):
    """ factory generate enemy effect """
    RATE = 3
    def __init__(self, node, datamap, *args, **keywords):
        frame = load_frame('res\\star.bmp', (4, 1), (0, 0, 0))
        frame = frame + (frame[3], ) + (frame[2], ) + (frame[1], ) + (frame[0], ) # change big, and then change small
        frame *= 2 # double
        super(StarEffect, self).__init__(node, datamap, (frame, ), 0, \
                                         StarEffect.RATE, deadfunc = self.pros_star)
        self.__args = args
        self.__keywords = keywords
        self.pdc_init()
        
    def pros_star(self):
        self.driver.send(ProduceSignal(0, Enemy, self.node, self.datamap, \
                                       *self.__args, **self.__keywords))
        self.pdc_producesig()

class HitEffect(Effect):
    """ bullet hit some thing effect """
    RATE = 3
    def __init__(self, node, datamap):
        frame = load_frame('res\\hit.bmp', (3, 1), (0, 0, 0))
        super(HitEffect, self).__init__(node, datamap, (frame, ), 0, HitEffect.RATE)
        
class BoomEffect(Effect):
    """ tank dead effect """
    RATE = 5
    def __init__(self, node, datamap):
        frame = load_frame('res\\boom.bmp', (3, 1), (0, 0, 0))
        super(BoomEffect, self).__init__(node, datamap, (frame, ), 0, BoomEffect.RATE)
        playsound('boom')
        
##################################################
# misc
##################################################
class Base(StaticSprite, VitalSprite, CollisionSprite):
    """ player's base, when this dead game is over """ 
    HP = 1
    HOLD = ((0, 0), (1, 0), (0, 1), (1, 1))
    ALIVE = 0
    DEAD = 1   
    def __init__(self, node, datamap):
        imagedict = load_frame('res\\base.bmp', (2, 1), (0, 0, 0))
        super(Base, self).__init__(node, datamap, Base.HOLD)
        self.stc_init(imagedict, Base.ALIVE)
        self.vtl_init(Base.HP)
        self.col_init()
        self.col_register((Bullet, ), self.pros_bulletcoll)
        
    def pros_bulletcoll(self, spr):
        self.vtl_change(spr.power)
        if not self.vtl_alive(): self.stc_change(Base.DEAD) # simple change a static image
    
    def update(self):
        self.col_collisionsig()
        super(Base, self).update()

class Bullet(StaticSprite, MovableSprite, CollisionSprite, ProducerSprite):
    """ tank's bullet """
    SPEED =  4
    HOLD = ( # up, left, down, right
            ((0, 0), (1, 0)),
            ((0, 0), (0, 1)),
            ((0, 0), (1, 0)),
            ((0, 0), (0, 1)),
            )
    FRONT = ((0, -1), (-1, 0), (0, 0), (0, 0)) # bullet's front, for effect
    def __init__(self, node, map, direct, power, speed = 4):
        ud = load_frame('res\\bulletUD.bmp', (1, 2), (0, 0, 0))
        lr = load_frame('res\\bulletLR.bmp', (2, 1), (0, 0, 0))
        image = (ud[0], lr[0], ud[1], lr[1])
        super(Bullet,self).__init__(node, map, Bullet.HOLD[direct])
        self.stc_init(image, direct)
        self.mov_init(speed)
        # generator path point
        for i in xrange(datamap.DataMap.XAXIS):
            self.driver.send(PathSignal(direct))
        self.col_init()
        self.col_register((None.__class__, Player, 
                           Plane, Enemy, Brick, 
                           Steel, Base, Bullet, BossEnemy), self.pros_allcoll)
        self.pdc_init()
        self.direct = direct
        self.power = power
        playsound('bullet')

    def pros_allcoll(self, spr = None):
        if spr != self.parent: # collided self parent that nothing hapend
            if (self.parent.__class__ == Player \
                or self.parent.__class__ == Plane) \
            and spr.__class__ == Enemy:
                self.parent.driver.send(ChildrenSignal(spr))
            self.kill()
    
    def kill(self):
        """ generate hit effect before die """
        node = (self.node[0] + Bullet.FRONT[self.direct][0], 
                self.node[1] + Bullet.FRONT[self.direct][1])
        self.driver.send(ProduceSignal(0, HitEffect, node, self.datamap))
        self.pdc_producesig()
        super(Bullet, self).kill()
    
    def update(self):
        self.col_collisionsig()
        if self.alive():
            self.mov_pathsig()        
            self.mov_gotosig()
        super(Bullet, self).update()

class Props(DynamicSprite, ThroughSprite, CollisionSprite):
    """ game props """
    RATE = 2
    LAYER = 3
    HOLD = ((0, 0), (1, 0), (0, 1), (1, 1))
    def __init__(self, node, datamap, image, proptime = 0, efftime = 0):
        frame = blend_range(image, (50, 50, 50), (255, 255, 255), 10) \
                + blend_range(image, (255, 255, 255), (50, 50, 50), 10)
        super(Props, self).__init__(node, datamap, hold = Props.HOLD, layer = Props.LAYER)
        self.proptime = proptime # prop show in map time
        self.efftime = efftime # prop effctive time
        self.proptick = 0
        self.efftick = 0
        self.effective = False # whether effective
        self.effspr = None # effctive sprite
        self.dnc_init((frame, ), 0, Props.RATE)
        self.tru_init()
        self.tru_register((Enemy, Bullet, Brick, Steel, Lawn, River, Ice))
        self.col_init()
        self.col_register((Player,Plane ), self.pros_prop)
    
    def pros_prop(self, spr):
        """ a sprite collided this prop """
        self.effective = True
        self.effspr = spr
        self.datamap.ref_delete(self)
        self.datamap.render.remove(self)
    
    def pros_unprop(self):
        """ a prop effect disappear """
        pass
    
    def update(self):
        self.col_collisionsig()
        self.dnc_animate()
        if not self.effective: # live timer
            if self.proptime > 0: # if limit
                self.proptick += 1
                if self.proptick >= self.proptime: self.kill()
        else: # effect timer
            if self.efftime == 0: # if no limit, kill
                self.kill()
            else: # if limit
                self.efftick += 1
                if self.efftick >= self.efftime:
                    if spr and spr.alive(): self.pros_unprop() # if sprite alive, discard the effect
                    self.kill()
        super(Props, self).update()
        
class PowerProp(Props):
    PROPTIME = 500
    EFFTIME = 0
    MAXPOWER = -3
    """ power max """
    def __init__(self, node, datamap):
        image = load_image('res\\power.bmp', (0, 0, 0))
        super(PowerProp, self).__init__(node, datamap, image, PowerProp.PROPTIME, PowerProp.EFFTIME)
    
    def pros_prop(self, spr):
        spr.power = PowerProp.MAXPOWER
        super(PowerProp, self).pros_prop(spr)

class VitalProp(Props):
    """ vital max """
    PROPTIME = 500
    EFFTIME = 0
    def __init__(self, node, datamap):
        image = load_image('res\\vital.bmp', (0, 0, 0))
        super(VitalProp, self).__init__(node, datamap, image, VitalProp.PROPTIME, VitalProp.EFFTIME)
    
    def pros_prop(self, spr):
        spr.vtl_change(3)
        super(VitalProp, self).pros_prop(spr)

class AlmightyProp(Props):
    """ power -1 and vital +1 """
    PROPTIME = 500
    EFFTIME = 0
    def __init__(self, node, datamap):
        image = load_image('res\\almighty.bmp', (0, 0, 0))
        super(AlmightyProp, self).__init__(node, datamap, image, AlmightyProp.PROPTIME, AlmightyProp.EFFTIME)
    
    def pros_prop(self, spr):
        spr.vtl_change(1)
        spr.power += -1
        if spr.power < PowerProp.MAXPOWER: spr.power = PowerProp.MAXPOWER
        super(AlmightyProp, self).pros_prop(spr)

##################################################
# tank
##################################################
class Tank(DynamicSprite, MovableSprite, VitalSprite, ProducerSprite, CollisionSprite):
    """ player and enemy tank's super class """
    HP = 1
    MAXHP = 3
    HOLD = ((0, 0), (1, 0), (0, 1), (1, 1))
    FRONT = ((0, -1), (-1, 0), (0, 2), (2, 0)) # tank's front node
    def __init__(self, node, frame, datamap, layer = 0):
        super(Tank, self).__init__(node, datamap, Tank.HOLD, layer = layer)
        self.dnc_init(frame, PathSignal.UP)
        self.mov_init(self.SPEED)
        self.vtl_init(Tank.HP, Tank.MAXHP)
        self.pdc_init()
        self.col_init()
        self.col_register((Bullet, ), self.pros_bulletcoll)
        self.direct = PathSignal.UP # default direct is up
        self.power = -1

    def pros_bulletcoll(self, spr):
        """ process bullet collided tank """
        pass
            
    def fire(self):
        """ bullet will shot tank's front """
        node = (self.node[0] + Tank.FRONT[self.direct][0], self.node[1] + Tank.FRONT[self.direct][1])
        self.driver.send(ProduceSignal(30, Bullet, node, self.datamap, self.direct, self.power))
        
    def kill(self):
        """ generate a boom effect before die """
        playsound('boom')
        node = (self.node[0] - 1, self.node[1] - 1) # top left
        self.driver.send(ProduceSignal(0, BoomEffect, node, self.datamap))
        self.pdc_producesig()
        super(Tank, self).kill()
        
class Player(Tank, KeyboardSprite):
    """ player tank """
    SPEED = 2
    KBD_DIRECT = (K_UP, K_DOWN, K_LEFT, K_RIGHT)
    KBD_FIRE = (K_SPACE, )
    def __init__(self, node, datamap, frame = None, layer = 0):
        if not frame:
            rawframe = load_frame('res\\tank.bmp', (8, 8), (0, 0, 0))
            frame = (rawframe[0:2], rawframe[2:4], rawframe[4:6], rawframe[6:8]) # up, left, down, right
            # blending color
            for sub in frame:
                for image in sub:
                    blend_color(image, (255, 255, 100))
        super(Player, self).__init__(node, frame, datamap, layer = layer)
        self.kbd_init()
        self.kbd_register(Player.KBD_DIRECT, self.pros_directkey)
        self.kbd_register(Player.KBD_FIRE, self.pros_firekey)
        self.killboss = 0
        self.kills = 0
        self.score = {}

    def killscore(self, spr):
        if not self.score.has_key(spr.itg_level()):
            self.score[spr.itg_level()] = []
        self.score[spr.itg_level()].append(spr)
        self.kills += 1

    def pros_bulletcoll(self, spr):
        """ process bullet collided tank """
        if spr.parent.__class__ == Enemy \
        or spr.parent.__class__ == BossEnemy: # there is no KILLSELF
            self.vtl_change(spr.power)
            if not self.vtl_alive(): 
                self.datamap.board.player_dead(self)
                self.kill()

    def pros_directkey(self, key):
        """ process keyboard signal """
        if not self.driver.check(GotoSignal.KIND):
            if key == K_UP:
                self.driver.send(PathSignal(PathSignal.UP, False), True)# not force path, and overwrite driver
            elif key == K_DOWN:
                self.driver.send(PathSignal(PathSignal.DOWN, False), True)
            elif key == K_LEFT:
                self.driver.send(PathSignal(PathSignal.LEFT, False), True)
            elif key == K_RIGHT:
                self.driver.send(PathSignal(PathSignal.RIGHT, False), True)
        self.driver.clear(KeySignal.KIND)
    
    def pros_firekey(self, key):
        if key == K_SPACE:
            self.fire()            
    
    def update(self):
        # process collision signal
        self.col_collisionsig()
        if self.vtl_alive():
            # process key signal
            self.kbd_keysig()
            # process path signal
            direct = self.mov_pathsig()
            if direct != None: 
                playsound('player', 0.3)
                self.direct = direct
                self.dnc_animate(direct) # if processed a new path, turn direct
            # process goto signal
            if self.mov_gotosig(): self.dnc_animate() # if moved ,animate
            # process produce signal
            self.pdc_producesig()
            # process children signal
            sigs = self.driver.receive(ChildrenSignal.KIND)
            for sig in sigs:
                self.killscore(sig.args[0])
            self.driver.clear(ChildrenSignal.KIND)
        # super class update
        super(Player, self).update()

class Plane(Player, ThroughSprite):
    LAYER = 3
    SPEED = 1
    KBD_DIRECT = (K_UP, K_DOWN, K_LEFT, K_RIGHT)
    KBD_FIRE = (K_SPACE, )
    def __init__(self, node, datamap):
        rawframe = load_frame('res\\plane.bmp', (4, 1), (0, 0, 0))
        frame = (rawframe[0:1], rawframe[1:2], rawframe[2:3], rawframe[3:4]) # up, left, down, right
        super(Plane, self).__init__(node, datamap, frame, layer = Plane.LAYER)
        self.tru_init()
        self.tru_register((Player, Brick, Steel, Enemy, Lawn, River, Ice ))
   

class Enemy(Tank, IntelligentSprite, DropSprite):
    """ enemy tank """
    SPEED = 1
    DROP = (
            (50, PowerProp, ()),
            (50, VitalProp, ()),
            (50, AlmightyProp, ()),
            )
    def __init__(self, node, datamap, ai, level, frame = None):
        if not frame:
            rawframe = load_frame('res\\tank.bmp', (8, 8), (0, 0, 0))
            frame = (rawframe[32:34], rawframe[34:36], rawframe[36:38], rawframe[38:40]) # up, left, down, right
            # blending color
            for sub in frame:
                for image in sub:
                    blend_color(image, (100, 255, 255))
        super(Enemy, self).__init__(node, frame, datamap)
        self.itg_init(ai, level)
        self.drp_init(Enemy.DROP)
        self.path = [node, ] # enemy path

    def pros_bulletcoll(self, spr):
        """ process bullet collided tank """
        if spr.parent.__class__ == Player \
        or spr.parent.__class__ == Plane: # there is no KILLSELF
            self.vtl_change(spr.power)
            if not self.vtl_alive(): self.kill()

    def kill(self):
        self.drp_drop()
        self.pdc_producesig()
        self.datamap.board.tanker_dead(self)
        super(Enemy, self).kill()
    
    def update(self):
        # process collision signal
        self.col_collisionsig()
        if self.vtl_alive():
            # process ai
            self.itg_think()
            # process path signal
            direct = self.mov_pathsig()
            if direct != None:
                self.direct = direct 
                self.dnc_animate(direct) # if processed a new path, turn direct
            # process goto signal
            if self.mov_gotosig(): self.dnc_animate() # if moved ,animate
            # process produce signal
            self.pdc_producesig()
            # super class update        
        super(Enemy, self).update()

class BossEnemy(Enemy):
    """ a boss level enemy """
    SPEED = 2
    def __init__(self, node, datamap, ai):
        rawframe = load_frame('res\\tank.bmp', (8, 8), (0, 0, 0))
        frame = (rawframe[56:58], rawframe[58:60], rawframe[60:62], rawframe[62:64]) # up, left, down, right
        # blending color
        for sub in frame:
            for image in sub:
                blend_color(image, (250, 100, 100))
        super(BossEnemy, self).__init__(node, datamap, ai, 6, frame)
        self.vtl_maxhp(5)
        self.vtl_change(3)
        playsound('boss')

    def fire(self):
        for i in xrange(4):
            node = (self.node[0] + Tank.FRONT[i][0], self.node[1] + Tank.FRONT[i][1])
            self.driver.send(ProduceSignal(0, Bullet, node, self.datamap, i, self.power, 8))

    def pros_bulletcoll(self, spr):
        """ process bullet collided tank """
        if spr.parent.__class__ == Player \
        or spr.parent.__class__ == Plane: # there is no KILLSELF
            self.vtl_change(spr.power)
            if not self.vtl_alive(): 
                spr.parent.killboss += 1
                self.kill()

class BossFactory(ProducerSprite):
    """ this factory can produce enemy by config """
    def __init__(self, node, datamap, time = 50):
        super(BossFactory, self).__init__(node, datamap, visible = False)
        self.__time = time
        self.pdc_init()
    
    def state(self):
        return self.__time // 50 # second
    
    def update(self):
        self.__time -= 1
        if self.__time <= 0:
            self.driver.send(ProduceSignal(0, BossEnemy, 
                                           self.node, self.datamap, 
                                           self.datamap.ai(self.datamap.board)))
            self.pdc_producesig()
            self.kill()
            

class EnemyFactory(ProducerSprite):
    """ this factory can produce enemy by config """
    def __init__(self, node, datamap, pdclist, rate = 107, maxenemy = 0):
        super(EnemyFactory, self).__init__(node, datamap, visible = False)
        self.__pdclist = []
        for item in pdclist: # convert to list
            self.__pdclist.append(list(item))
        self.__rate = rate
        self.__max = maxenemy # alive enemy max number that this factory produced
        self.__tick = 0
        self.pdc_init()
    
    def state(self):
        return tuple(self.__pdclist)
    
    def update(self):
        if self.__max == 0 or len(self.children) < self.__max: # 0 means no limit
            self.__tick = (self.__tick + 1) % self.__rate
            if self.__tick == 0: # if cold down
                index = random.randint(0, len(self.__pdclist) - 1)
                self.driver.send(ProduceSignal(0, StarEffect, self.node, self.datamap, \
                                                self.datamap.ai(self.datamap.board), self.__pdclist[index][1]))
                self.__pdclist[index][0] -= 1
                if self.__pdclist[index][0] <= 0: del self.__pdclist[index]
        # process produce signal
        self.pdc_producesig()
        # if producer compeleted
        if not self.__pdclist: self.kill()
        # there no super update, because this's not visible


















